﻿using System.Globalization;
using System.Text;
using NHibernate.Sidekick.Data.Domain;
using NHibernate.Validator.Engine;
using NHibernate.Validator.Exceptions;

namespace NHibernate.Sidekick.Data.NHibernate
{
    /// <summary>
    /// Provides database operations for the specified type.
    /// </summary>
    /// <typeparam name="T">The specified type.</typeparam>
    /// <typeparam name="TId">The type of the id.</typeparam>
    public class Dao<T, TId> : IDao<T, TId> where T : DomainObject<T, TId>
    {
        #region NHibernate Validator Engine
        private static ValidatorEngine _validatorEngine;
        private static ValidatorEngine ValidatorEngine
        {
            get { return _validatorEngine ?? (ValidatorEngine = new ValidatorEngine()); }
            set { _validatorEngine = value; }
        }

        private static void ValidateEntityValues(T entity)
        {
            if (!ValidatorEngine.IsValid(entity))
            {
                var invalidValues = ValidatorEngine.Validate(entity);
                var message = new StringBuilder();
                foreach (var invalidValue in invalidValues)
                {
                    message.AppendLine(string.Format(CultureInfo.InvariantCulture, "{0}: {1}",
                                                     invalidValue.PropertyName,
                                                     invalidValue.Message));
                }
                throw new HibernateValidatorException(message.ToString());
            }
        }

        #endregion
        
        /// <summary>
        /// Used to obtain the <see cref="System.Type"/> object for a type.
        /// </summary>
        /// <value>The type.</value>
        private static System.Type PersitentType
        {
            get { return typeof(T); }
        }

        /// <summary>
        /// Gets the main runtime interface between a .NET application and NHibernate.
        /// </summary>
        /// <value>The main <see cref="ISession"/>.</value>
        private static ISession NHibernateSession
        {
            get { return SessionManager.Instance.GetSession(); }
        }

        /// <summary>
        /// Persist the given transient instance, first assigning a generated identifier.
        /// </summary>
        /// <param name="entity">The transient entity.</param>
        /// <returns>The saved instance.</returns>
        public TId Save(T entity)
        {
            ValidateEntityValues(entity);
            TId id = (TId) NHibernateSession.Save(entity);

            return id;
        }

        /// <summary>
        /// Update the persistent instance with the identifier of the given detached instance.
        /// </summary>
        /// <param name="entity">The transient entity.</param>
        /// <returns>The updated instance.</returns>
        public void Update(T entity)
        {
            ValidateEntityValues(entity);
            NHibernateSession.Update(entity);
        }

        /// <summary>
        /// Either <see cref="Save(T)"/> or <see cref="Update(T)"/> the given instance, depending upon resolution of the unsaved-value checks
        /// </summary>
        /// <param name="entity">A transient or detached instance containing new or updated state .</param>
        /// <returns>The operated instance.</returns>
        public void SaveOrUpdate(T entity)
        {
            ValidateEntityValues(entity);
            NHibernateSession.SaveOrUpdate(entity);
        }

        /// <summary>
        /// Copy the state of the given object onto the persistent object with the same identifier. 
        /// If there is no persistent instance currently associated with the session, it will be loaded. 
        /// Return the persistent instance. If the given instance is unsaved or does not exist in the database, 
        /// save it and return it as a newly persistent instance. Otherwise, the given instance does not become 
        /// associated with the session.
        /// </summary>
        public TId SaveOrUpdateCopy(T entity)
        {
            ValidateEntityValues(entity);
            TId id = (TId) NHibernateSession.SaveOrUpdateCopy(entity);
            return id;
        }

        /// <summary>
        /// Remove a persistent instance from the datastore. The argument may be an instance associated
        /// with the receiving Session or a transient instance with an identifier associated with existing
        /// persistent state. This operation cascades to associated instances if the association is mapped
        /// with cascade="delete". 
        /// </summary>
        /// <param name="entity">The instance to be removed.</param>
        public void Delete(T entity)
        {
            NHibernateSession.Delete(entity);
        }

        /// <summary>
        /// Remove a persistent instance from the datastore. The argument may be an instance associated
        /// with the receiving Session or a transient instance with an identifier associated with existing
        /// persistent state. This operation cascades to associated instances if the association is mapped
        /// with cascade="delete". 
        /// </summary>
        /// <param name="id">The instance's identifier.</param>
        public void Delete(TId id)
        {
            NHibernateSession.Delete(NHibernateSession.Load<T>(id));
        }

        /// <summary>
        /// Flush the associated ISession and end the unit of work.
        /// </summary>
        public void CommitChanges()
        {
            if (SessionManager.Instance.HasOpenTransaction())
            {
                SessionManager.Instance.CommitTransaction();
            }
            else
            {
                // If there's no transaction, just flush the changes
                SessionManager.Instance.GetSession().Flush();
            }
        }

        /// <summary>
        /// Forces the underlying <see cref="ITransaction"/> to roll back.
        /// </summary>
        public void RollBackChanges()
        {
            if (SessionManager.Instance.HasOpenTransaction())
            {
                SessionManager.Instance.RollbackTransaction();
            }
        }

        /// <summary>
        /// Re-read the state of the given instance from the underlying database.
        /// </summary>
        /// <param name="entity">The persistent instance.</param>
        public void Refresh(T entity)
        {
            SessionManager.Instance.GetSession().Refresh(entity);
        }
    }
}
